1 /** 2 This module is an attempt to alleviate compile times by including the bare 3 minimum. The idea is that while the reporting usually done by unit-threaded 4 is welcome, it only really matters when tests fail. Otherwise, no news is 5 good news. 6 7 Likewise, naming and selecting tests are features used when certain tests 8 fail. The usual way to run tests is to run all of them and be happy if 9 they all pass. 10 11 This module makes it so that unit-threaded gets out of the way, and if 12 needed the full features can be turned on at the cost of compiling 13 much more slowly. 14 15 There aren't even any template constraints on the `should` functions 16 to avoid imports as much as possible. 17 */ 18 module unit_threaded.light; 19 20 alias UnitTestException = Exception; 21 22 /** 23 Dummy version of runTests so "normal" code compiles. 24 */ 25 int runTests(T...)(in string[] args) { 26 return runTestsImpl; 27 } 28 29 /// ditto 30 int runTests(T)(string[] args, T testData) { 31 return runTestsImpl; 32 } 33 34 int runTestsImpl() { 35 import core.runtime : Runtime; 36 import core.stdc.stdio : printf; 37 38 try { 39 40 Runtime.moduleUnitTester(); 41 42 printf("\n"); 43 version (Posix) 44 printf("\033[32;1mOk\033[0;;m"); 45 else 46 printf("Ok"); 47 48 printf(": All tests passed\n\n"); 49 50 return 0; 51 } catch (Throwable _) 52 return 1; 53 } 54 55 /** 56 Dummy version so "normal" code compiles 57 */ 58 int[] allTestData(T...)() { 59 return []; 60 } 61 62 /** 63 No-op version of writelnUt 64 */ 65 void writelnUt(T...)(auto ref T args) { 66 67 } 68 69 /** 70 Same as unit_threaded.property.check 71 */ 72 void check(alias F)(int numFuncCalls = 100, in string file = __FILE__, in size_t line = __LINE__) @trusted { 73 import unit_threaded.property : utCheck = check; 74 75 utCheck!F(numFuncCalls, file, line); 76 } 77 78 /** 79 Same as unit_threaded.property.checkCustom 80 */ 81 void checkCustom(alias Generator, alias Predicate)(int numFuncCalls = 100, 82 in string file = __FILE__, in size_t line = __LINE__) @trusted { 83 import unit_threaded.property : utCheckCustom = checkCustom; 84 85 utCheckCustom!(Generator, Predicate)(numFuncCalls, file, line); 86 } 87 88 /** 89 Generic output interface 90 */ 91 interface Output { 92 void send(in string output) @safe; 93 void flush() @safe; 94 } 95 96 /** 97 Dummy version of unit_threaded.testcase.TestCase 98 */ 99 class TestCase { 100 abstract void test(); 101 void setup() { 102 } 103 104 void shutdown() { 105 } 106 107 static TestCase currentTest() { 108 return new class TestCase { 109 override void test() { 110 } 111 }; 112 } 113 114 Output getWriter() { 115 return new class Output { 116 override void send(in string output) { 117 } 118 119 override void flush() { 120 } 121 }; 122 } 123 } 124 125 /** 126 Same as unit_threaded.mock.mock 127 */ 128 auto mock(T)() { 129 import unit_threaded.mock : utMock = mock; 130 131 return utMock!T; 132 } 133 134 /** 135 Same as unit_threaded.mock.mockStruct 136 */ 137 auto mockStruct(T...)(auto ref T returns) { 138 import unit_threaded.mock : utMockStruct = mockStruct; 139 140 return utMockStruct(returns); 141 } 142 143 /** 144 Throw if condition is not true. 145 */ 146 void shouldBeTrue(E)(lazy E condition, in string file = __FILE__, in size_t line = __LINE__) { 147 assert_(cast(bool) condition(), file, line); 148 } 149 150 /// Throw if condition not false. 151 void shouldBeFalse(E)(lazy E condition, in string file = __FILE__, in size_t line = __LINE__) { 152 assert_(!cast(bool) condition(), file, line); 153 } 154 155 /// Assert value is equal to expected 156 void shouldEqual(V, E)(auto ref V value, auto ref E expected, in string file = __FILE__, 157 in size_t line = __LINE__) { 158 159 void checkInputRange(T)(auto ref const(T) _) @trusted { 160 auto obj = cast(T) _; 161 bool e = obj.empty; 162 auto f = obj.front; 163 obj.popFront; 164 } 165 166 enum isInputRange(T) = is(T : Elt[], Elt) || is(typeof(checkInputRange(T.init))); 167 168 static if (is(V == class)) { 169 assert_(value.tupleof == expected.tupleof, file, line); 170 } else static if (isInputRange!V && isInputRange!E) { 171 auto ref unqual(T)(auto ref const(T) obj) @trusted { 172 static if (is(T == void[])) 173 return cast(ubyte[]) obj; 174 else 175 return cast(T) obj; 176 } 177 178 import std.algorithm : equal; 179 180 assert_(equal(unqual(value), unqual(expected)), file, line); 181 } else { 182 assert_(cast(const) value == cast(const) expected, file, line); 183 } 184 } 185 186 /// Assert value is not equal to expected. 187 void shouldNotEqual(V, E)(in auto ref V value, in auto ref E expected, 188 in string file = __FILE__, in size_t line = __LINE__) { 189 assert_(value != expected, file, line); 190 } 191 192 /// Assert value is null. 193 void shouldBeNull(T)(in auto ref T value, in string file = __FILE__, in size_t line = __LINE__) { 194 assert_(value is null, file, line); 195 } 196 197 /// Assert value is not null 198 void shouldNotBeNull(T)(in auto ref T value, in string file = __FILE__, in size_t line = __LINE__) { 199 assert_(value !is null, file, line); 200 } 201 202 enum isLikeAssociativeArray(T, K) = is(typeof({ 203 if (K.init in T) { 204 } 205 if (K.init !in T) { 206 } 207 })); 208 static assert(isLikeAssociativeArray!(string[string], string)); 209 static assert(!isLikeAssociativeArray!(string[string], int)); 210 211 /// Assert that value is in container. 212 void shouldBeIn(T, U)(in auto ref T value, in auto ref U container, 213 in string file = __FILE__, in size_t line = __LINE__) 214 if (isLikeAssociativeArray!(U, T)) { 215 assert_(cast(bool)(value in container), file, line); 216 } 217 218 /// ditto. 219 void shouldBeIn(T, U)(in auto ref T value, U container, in string file = __FILE__, 220 in size_t line = __LINE__) if (!isLikeAssociativeArray!(U, T)) { 221 import std.algorithm : find; 222 import std.array : empty; 223 224 assert_(!find(container, value).empty, file, line); 225 } 226 227 /// Assert value is not in container. 228 void shouldNotBeIn(T, U)(in auto ref T value, in auto ref U container, 229 in string file = __FILE__, in size_t line = __LINE__) 230 if (isLikeAssociativeArray!U) { 231 assert_(!cast(bool)(value in container), file, line); 232 } 233 234 /// ditto. 235 void shouldNotBeIn(T, U)(in auto ref T value, U container, in string file = __FILE__, 236 in size_t line = __LINE__) if (!isLikeAssociativeArray!(U, T)) { 237 import std.algorithm : find; 238 import std.array : empty; 239 240 assert_(find(container, value).empty, file, line); 241 } 242 243 /// Assert that expr throws. 244 void shouldThrow(T : Throwable = Exception, E)(lazy E expr, 245 in string file = __FILE__, in size_t line = __LINE__) { 246 auto threw = false; 247 () @trusted{ 248 try { 249 expr(); 250 } catch (T _) { 251 threw = true; 252 } 253 }(); 254 assert_(threw, file, line); 255 } 256 257 /// Assert that expr throws an Exception that must have the type E, derived types won't do. 258 void shouldThrowExactly(T : Throwable = Exception, E)(lazy E expr, 259 in string file = __FILE__, in size_t line = __LINE__) { 260 T throwable = null; 261 262 () @trusted{ 263 try { 264 expr(); 265 assert_(false, file, line); 266 } catch (T t) { 267 throwable = t; 268 } 269 }(); 270 271 //Object.opEquals is @system and impure 272 const sameType = () @trusted{ 273 return throwable !is null && typeid(throwable) == typeid(T); 274 }(); 275 assert_(sameType, file, line); 276 277 } 278 279 /// Assert that expr doesn't throw 280 void shouldNotThrow(T : Throwable = Exception, E)(lazy E expr, 281 in string file = __FILE__, in size_t line = __LINE__) { 282 () @trusted{ 283 try 284 expr(); 285 catch (T _) 286 assert_(false, file, line); 287 }(); 288 } 289 290 /// Assert that expr throws and the exception message is msg. 291 void shouldThrowWithMessage(T : Throwable = Exception, E)(lazy E expr, string msg, 292 string file = __FILE__, size_t line = __LINE__) { 293 T throwable = null; 294 295 () @trusted{ 296 try { 297 expr(); 298 } catch (T ex) { 299 throwable = ex; 300 } 301 }(); 302 303 assert_(throwable !is null && throwable.msg == msg, file, line); 304 } 305 306 /// Assert that value is approximately equal to expected. 307 void shouldApproxEqual(V, E)(in V value, in E expected, string file = __FILE__, 308 size_t line = __LINE__) { 309 import std.math : approxEqual; 310 311 assert_(approxEqual(value, expected), file, line); 312 } 313 314 /// assert that rng is empty. 315 void shouldBeEmpty(R)(in auto ref R rng, in string file = __FILE__, in size_t line = __LINE__) { 316 import std.range : isInputRange; 317 import std.traits : isAssociativeArray; 318 import std.array; 319 320 static if (isInputRange!R) 321 assert_(rng.empty, file, line); 322 else static if (isAssociativeArray!R) 323 () @trusted{ assert_(rng.keys.empty, file, line); }(); 324 else 325 static assert(false, "Cannot call shouldBeEmpty on " ~ R.stringof); 326 } 327 328 /// Assert that rng is not empty. 329 void shouldNotBeEmpty(R)(in auto ref R rng, in string file = __FILE__, in size_t line = __LINE__) { 330 import std.range : isInputRange; 331 import std.traits : isAssociativeArray; 332 import std.array; 333 334 static if (isInputRange!R) 335 assert_(!rnd.empty, file, line); 336 else static if (isAssociativeArray!R) 337 () @trusted{ assert_(!rng.keys.empty, file, line); }(); 338 else 339 static assert(false, "Cannot call shouldBeEmpty on " ~ R.stringof); 340 } 341 342 /// Assert that t should be greater than u. 343 void shouldBeGreaterThan(T, U)(in auto ref T t, in auto ref U u, 344 in string file = __FILE__, in size_t line = __LINE__) { 345 assert_(t > u, file, line); 346 } 347 348 /// Assert that t should be smaller than u. 349 void shouldBeSmallerThan(T, U)(in auto ref T t, in auto ref U u, 350 in string file = __FILE__, in size_t line = __LINE__) { 351 assert_(t < u, file, line); 352 } 353 354 /// Assert that value is the same set as expected (i.e. order doesn't matter) 355 void shouldBeSameSetAs(V, E)(in auto ref V value, in auto ref E expected, 356 in string file = __FILE__, in size_t line = __LINE__) { 357 assert_(isSameSet(value, expected), file, line); 358 } 359 360 /// Assert that value is not the same set as expected. 361 void shouldNotBeSameSetAs(V, E)(in auto ref V value, in auto ref E expected, 362 in string file = __FILE__, in size_t line = __LINE__) { 363 assert_(!isSameSet(value, expected), file, line); 364 } 365 366 private bool isSameSet(T, U)(in auto ref T t, in auto ref U u) { 367 import std.array : array; 368 import std.algorithm : canFind; 369 370 //sort makes the element types have to implement opCmp 371 //instead, try one by one 372 auto ta = t.array; 373 auto ua = u.array; 374 if (ta.length != ua.length) 375 return false; 376 foreach (element; ta) { 377 if (!ua.canFind(element)) 378 return false; 379 } 380 381 return true; 382 } 383 384 /// Assert that actual and expected represent the same JSON (i.e. formatting doesn't matter) 385 void shouldBeSameJsonAs(in string actual, in string expected, 386 in string file = __FILE__, in size_t line = __LINE__) @trusted // not @safe pure due to parseJSON 387 { 388 import std.json : parseJSON, JSONException; 389 390 auto parse(in string str) { 391 try 392 return str.parseJSON; 393 catch (JSONException ex) { 394 assert_(false, "Failed to parse " ~ str, file, line); 395 } 396 assert(0); 397 } 398 399 assert_(parse(actual) == parse(expected), file, line); 400 } 401 402 private void assert_(in bool value, in string file, in size_t line) @safe pure { 403 assert_(value, "Assertion failure", file, line); 404 } 405 406 private void assert_(bool value, in string message, in string file, in size_t line) @trusted pure { 407 if (!value) 408 throw new Exception(message, file, line); 409 } 410 411 void fail(in string output, in string file, in size_t line) @safe pure { 412 assert_(false, output, file, line); 413 }